package com.google.code.gaestats;

import com.google.appengine.api.memcache.MemcacheService;
import static com.google.code.gaestats.StatsObjectifyFactory.ms;
import static com.google.code.gaestats.StatsObjectifyFactory.ofy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.java.Log;

/**
 *
 * @author JaggardM
 */
@Log
public class GAEStats extends HttpServlet
{

	public static int SECONDS_BETWEEN_PERSISTANCE = 60;
	static CounterNames counterNames;

	static
	{
		counterNames = ofy().getCounterNames();
		if (counterNames == null)
		{
			counterNames = new CounterNames();
		}
	}

	public static HitCounter counter(String name)
	{
		return new HitCounter(name);
	}

	public static ValuedCounter valued(String name)
	{
		return new ValuedCounter(name);
	}

	public static Map<String, UpdateData> runEachMinute()
	{
		List<Counter> counters = getAllDataForAllCounters();
		Map<String, UpdateData> results = new HashMap<String, UpdateData>();
		for (Counter cntr : counters)
		{
			cntr.persistData();
		}
		return results;
	}

	@Override
	public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException
	{
		Object uriO = req.getAttribute("javax.servlet.forward.request_uri");
		String uri;
		if (uriO == null)
		{
			uri = req.getRequestURI();
		}
		else
		{
			uri = uriO.toString();
		}
		if (uri.contains("perMinute"))
		{
			try
			{
				runEachMinute();
				resp.setStatus(200);
			}
			catch (Throwable t)
			{
				log.log(Level.SEVERE, "Unknown error", t);
				resp.setStatus(500);
			}
		}
		else if (uri.contains("ajax"))
		{
			//TODO: Check that callback only contains a-z chars
			if (uri.contains("hour"))
			{
				resp.getWriter().println(getHourJsonDataAJAX(req.getParameter("name"), req.getParameter("callback")));
			}
			else if (uri.contains("minute"))
			{
				resp.getWriter().println(getMinuteJsonDataAJAX(req.getParameter("name"), req.getParameter("callback")));
			}
		}
	}

	public String getHourJsonDataAJAX(String name, String callbackFunction)
	{
		StringBuilder sb = new StringBuilder();
		sb.append(callbackFunction);
		sb.append("(");
		Counter counter = getAllDataForCounter(name);
		sb.append(counter.hourData.get().googleJavascriptData());
		sb.append(");");
		return sb.toString();
	}

	public String getMinuteJsonDataAJAX(String name, String callbackFunction)
	{
		StringBuilder sb = new StringBuilder();
		sb.append(callbackFunction);
		sb.append("(");
		Counter counter = getMinuteDataForCounter(name);
		sb.append(counter.minuteData.get().googleJavascriptData());
		sb.append(");");
		return sb.toString();
	}

	public String getJsonData(String name, boolean minuteOnly)
	{
		StringBuilder sb = new StringBuilder();
		Counter counter;
		if (minuteOnly)
		{
			counter = getMinuteDataForCounter(name);
		}
		else
		{
			counter = getAllDataForCounter(name);
			sb.append("var ");
			sb.append(name);
			sb.append("_hour = ");
			sb.append(counter.hourData.get().googleJavascriptData());
			sb.append(";\r\n");
		}
		sb.append("var ");
		sb.append(name);
		sb.append("_minute = ");
		sb.append(counter.minuteData.get().googleJavascriptData());
		sb.append(";\r\n");
		return sb.toString();
	}

	private static List<Counter> getAllDataForAllCounters()
	{
		fixCounterNames(0);
		Map<String, Counter> counters = ofy().loadAllDataForCounters(GAEStats.counterNames.getNames());
		return fillInMissingCounters(counters);
	}

	private static Counter getMinuteDataForCounter(String name)
	{
		fixCounterNames(0);
		Map<String, Counter> counters = ofy().loadMinuteDataForCounters(name);
		Counter cntr = counters.get(name);
		return fillMissingCounter(cntr, name);
	}

	private static Counter getAllDataForCounter(String name)
	{
		fixCounterNames(0);
		Map<String, Counter> counters = ofy().loadAllDataForCounters(name);
		Counter cntr = counters.get(name);
		return fillMissingCounter(cntr, name);
	}

	private static List<Counter> fillInMissingCounters(Map<String, Counter> counters)
	{
		List<Counter> toReturn = new ArrayList<Counter>();
		for (String name : GAEStats.counterNames.getNames())
		{
			Counter cntr = counters.get(name);
			toReturn.add(fillMissingCounter(cntr, name));
		}
		return toReturn;
	}

	private static Counter fillMissingCounter(Counter cntr, String name)
	{
		if (cntr == null)
		{
			for (CounterFactory cf : Counter.factories)
			{
				cntr = cf.get(name);
				if (cntr != null)
				{
					break;
				}
			}
		}
		return cntr;
	}

	private static void fixCounterNames(int repetitionNumber)
	{
		if (repetitionNumber > 20)
		{
			return;
		}
		MemcacheService.IdentifiableValue qtyIV = ms().getIdentifiable(Counter.COUNTER_NAME_QUANTITY);
		if (qtyIV != null)
		{
			Object qtyO = qtyIV.getValue();
			if (qtyO != null && qtyO instanceof Number)
			{
				int numKeys = ((Number) qtyO).intValue();
				List<String> keys = new ArrayList<String>(numKeys);
				for (int i = 0; i <= numKeys; i++)
				{
					keys.add(Counter.COUNTER_NAME_PREFIX + i);
				}
				Map<String, Object> pageNames = ms().getAll(keys);
				if (ms().putIfUntouched(Counter.COUNTER_NAME_QUANTITY, qtyIV, 0))
				{
					if (GAEStats.counterNames == null)
					{
						GAEStats.counterNames = ofy().getCounterNames();
					}
					boolean oneUpdated = false;
					for (Object name : pageNames.values())
					{
						if (name != null && GAEStats.counterNames.getNames().addIfAbsent(name.toString()))
						{
							oneUpdated = true;
						}
					}
					if (oneUpdated)
					{
						ofy().putCounterNames(GAEStats.counterNames);
					}
					ms().deleteAll(keys);
				}
				else
				{
					fixCounterNames(repetitionNumber + 1);
				}
			}
		}
	}
}
